设计模式之责任链[下]

上一篇责任链的博文说明了责任链模式的定义、模型、应用场景以及优缺点,这一篇说明责任链模式在iOS事件处理中的具体应用 – iOS之响应链The Responder Chain)。





响应者(Responders)

响应者是一个对象,它能够通过响应链,直接或者间接地接收事件(events),只需要继承NSResponder类,它就是责任链模式中的Handler。iOS Cocoa框架下的很多类:NSApplication, NSWindow, NSDrawer, NSWindowController, NSView等都继承NSResponder。NSResponder类定义了处理事件消息的程序化接口和响应动作的大致结构。响应链上有一个第一响应者和后续的一系列响应者。

第一响应者(First Reponders)

第一响应者一般是与用户交互的对象,比方说用户选择或者操作鼠标、键盘的操作。它往往是响应链中接收事件或者消息的第一个对象。一个NSWindow对象的第一响应者一般是它自己。但是,当应用程序第一次在设备屏幕上显示窗口的时候,你也可以在程序中或者在Interface Builder中手动设置第一响应者。

当一个NSWindow对象接收到一个鼠标按下的事件时,它一般会设法把NSView对象设置成第一响应者。它首先会询问当前的view是否愿意成为第一响应者,通过使用acceptsFirstResponder方法。这个方法一般返回NO,想要成为第一响应者的响应者需要重写Responder的这个方法,并且返回Yes。此外,当用户通过键盘接口改变第一响应者的时候,acceptsFirstResponder方法也会被调用。

你可以通过发送makeFirstResponder:消息给一个NSWindow对象,改变第一响应者。这个消息使得一个对象失去了它的第一响应者身份,而另一个对象成为了第一响应者。

一个NSPanel对象提供了一种第一响应者行为的变种,它允许面板(panels)获得主窗口(main window)的焦点。假设一个面板现在代表一个非活动(inactive)的窗口,当becomesKeyOnlyIfNeeded 方法接收到一个鼠标按下的事件的时候,它返回一个YES。它尝试将鼠标光标下的视图变为第一响应者。当且仅当调用方法 acceptsFirstResponder和needsPanelToBecomeKey返回YES的时候,才能够成功将光标下的视图变为第一响应者。

下一个响应者(Next Responders)

每个响应对象都有一个能力,就是获得它在响应链上的下一个响应者,也就是责任链中将消息往前推的默认操作。 响应链的最主要的机制,就是返回响应者对象的nextResponder方法。图3-1显示了下一个响应者的序列。

绝大数的响应链上,一个view的下一个响应者一般是它的superview。实际上,一个窗口的所有视图,由它的第一个响应者到它的内容视图链组成。当你用Interface Builder或者手动写代码创建一个窗口或者在已经存在的视图上添加子视图的时候,Application Kit自动将新建的对象添加到响应链中。NSView对象的addSubView:方法自动将接收者设置成新的子视图的superview。当你想要在这些视图之间插入一个不一样的响应者,必须保证正确地修正整个响应链,以适应新的变化。

响应链

响应链将一系列的响应对象串联起来,这些响应对象接收事件或者消息的传递。当一个给定的响应对象不处理一个特定的消息的时候,这个对象会将当前消息传递给它在响应链上的继承者(也就是它的下一个响应者)。这就使得响应对象能够代理其他对象的责任,尤其是更高等级的对象。Application Kit按照以下将要说道的方法自动构建响应链,但是你也可以将一般的对象插入到其中。

一个应用可以包含任何数量的响应链,但是在一个给定的时间,只有一条链是活动的。事件消息和动作消息的响应链是不一样的,下面将详细描述它们的具体操作情况。

事件消息的响应链

几乎所有的事件消息都使用一个单一窗口的响应链 – 相关用户事件发生的所在窗口。事件消息的默认响应链从NSWindow第一个传递消息的视图开始。一个键盘按键的默认响应链从一个窗口的第一响应者开始;一个鼠标或者触控板的默认响应链从用户事件发生的视图开始。如果在那儿,事件不被处理,事件消息将会沿着视图层次向前传递。第一个响应者一般是窗口中被选择的视图对象,而且它的下一个响应者是包含它的视图(superview),等等直到NSwindow对象为止。如果一个NSWindowController对象管理这个窗口,它就是最后的响应者。你可以在这些NSView对象之间插入其他响应者,甚至是在NSWindow对象上面,也就是响应链的顶部。这些被插入的响应者既接收事件消息,也接收动作消息。如果没有对象处理事件,那么响应链的最后一个响应者会调用方法noResponderFor:,代表按键按下事件的终止。当然,事件处理对象(一般是NSWindow和NSView的子类)可以重写这个方法,从而执行一些想要执行的附加操作。

动作消息的响应链

对于动作信息,Application Kit构建了一个更加详尽的响应链,基于以下两个因素:

  • 当前应用是否基于文件架构,如果不是,那么它是否使用 NSWindowController 对象构建窗口
  • 当前应用是否将key window作为主window展示

由于动作需要更加灵活的运行时机制来决定它们的目标,动作消息拥有更加详细的响应链。它们不像事件消息,仅仅限制在一个窗口中。

最简单的情况是,一个活动的没有基于文件的窗口,而且没有相关的面板或者次窗口;换句话说,主窗口也是关键窗口(key window)。这种情况下,响应链如下所示:

  1. 主窗口的第一响应者和连续的响应对象与视图层次架构一致
  2. 主窗口自己
  3. 主窗口的代理(无需继承NSResponder)
  4. 应用对象,NSApp
  5. 应用对象的代理(无需继承NSResponder)

整条链如图4-1所示

总结

本文说明了责任链模式在iOS框架Cocoa下的应用 – 响应链。以及,事件消息和动作消息如何在响应链上传递。